package cn.juque.lucenecandy.helper;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.YesOrNoEnum;
import cn.juque.common.exception.AppException;
import cn.juque.common.utils.FastJsonUtil;
import cn.juque.lucenecandy.bo.IndexBO;
import cn.juque.lucenecandy.bo.IndexUpdateParamBO;
import cn.juque.lucenecandy.cache.IndexReaderCache;
import cn.juque.lucenecandy.core.base.*;
import cn.juque.lucenecandy.core.constants.StrConstant;
import cn.juque.lucenecandy.core.datasync.DataSyncRender;
import cn.juque.lucenecandy.core.enums.DocSyncTypeEnum;
import cn.juque.lucenecandy.core.enums.FieldFillEnum;
import cn.juque.lucenecandy.core.handlers.EntityMetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.index.IndexNotFoundException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>IndexHelper</li>
 * </ul>
 * @date 2023-12-01 11:49:06
 **/
@Slf4j
@Component("indexHelper")
public class IndexHelper {

    @Resource
    private LuceneHelper luceneHelper;

    @Resource
    private IndexReaderCache indexReaderCache;

    @Resource
    private DocumentHelper documentHelper;

    @Resource
    private EntityMetaObjectHandler entityMetaObjectHandler;

    @Resource
    private DataSyncRender dataSyncRender;

    /**
     * 分页查询
     *
     * @param wrapper 查询条件
     * @return list
     */
    public <T extends BaseEntity> List<T> searchByPage(@NotNull QueryWrapper wrapper) {
        Assert.notNull(wrapper);
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        wrapper.getQueryMap().forEach(builder::add);
        IndexBO indexBO = wrapper.getIndexInfo();
        List<Document> list = this.luceneHelper.searchByPage(indexBO.getIndexName(), builder, wrapper.getSort(), wrapper.getPageInfo(), wrapper.getFieldToLoad());
        return this.documentHelper.toEntityList(list, ClassUtil.loadClass(wrapper.getClassName()));
    }

    /**
     * 查询
     *
     * @param queryWrapper 查询条件
     * @return list
     */
    public <T extends BaseEntity> List<T> search(@NotNull QueryWrapper queryWrapper) {
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        queryWrapper.getQueryMap().forEach(builder::add);
        IndexBO indexBO = queryWrapper.getIndexInfo();
        List<Document> list = this.luceneHelper.search(
                indexBO.getIndexName(),
                builder, queryWrapper.getSort(), queryWrapper.getFieldToLoad());
        return this.documentHelper.toEntityList(list, ClassUtil.loadClass(queryWrapper.getClassName()));
    }

    /**
     * 主键查询
     *
     * @param queryWrapper 查询条件
     * @return Document
     */
    public <T extends BaseEntity> T searchById(@NotNull IdQueryWrapper queryWrapper) {
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        queryWrapper.getQueryMap().forEach(builder::add);
        IndexBO indexBO = queryWrapper.getIndexInfo();
        List<Document> list = this.luceneHelper.search(
                indexBO.getIndexName(), builder, Sort.INDEXORDER, queryWrapper.getFieldToLoad());
        List<T> tList = this.documentHelper.toEntityList(list, ClassUtil.loadClass(queryWrapper.getClassName()));
        return CollUtil.isEmpty(tList) ? null : tList.get(0);
    }

    /**
     * 主键集合查询
     *
     * @param queryWrapper 查询条件
     * @return Document
     */
    public <T extends BaseEntity> List<T> searchByIds(IdsQueryWrapper queryWrapper) {
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        queryWrapper.getQueryMap().forEach(builder::add);
        IndexBO indexBO = queryWrapper.getIndexInfo();
        List<Document> list = this.luceneHelper.search(indexBO.getIndexName(), builder, Sort.RELEVANCE, queryWrapper.getFieldToLoad());
        return this.documentHelper.toEntityList(list, ClassUtil.loadClass(queryWrapper.getClassName()));
    }

    /**
     * 数量统计
     *
     * @param queryWrapper 查询条件
     * @return Long
     */
    public Long count(CountQueryWrapper queryWrapper) {
        try {
            IndexBO indexBO = queryWrapper.getIndexInfo();
            IndexReader indexReader = this.indexReaderCache.get(indexBO.getIndexName(), false);
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            BooleanQuery.Builder builder = new BooleanQuery.Builder();
            queryWrapper.getQueryMap().forEach(builder::add);
            return ((Integer) indexSearcher.count(builder.build())).longValue();
        } catch (IndexNotFoundException e) {
            return 0L;
        } catch (Exception e) {
            log.error("document count error", e);
        }
        throw new AppException(MessageEnum.SYSTEM_ERROR);
    }

    /**
     * 新增文档
     *
     * @param entity 实体类
     * @return Boolean
     */
    public <T extends BaseEntity> T addDocument(T entity) {
        IndexBO indexBO = this.documentHelper.index(entity.getClass());
        this.entityMetaObjectHandler.fill(FieldFillEnum.INSERT, indexBO, entity);
        IndexUpdateParamBO paramBO = this.buildIndexUpdateParamBO(indexBO, CollUtil.newArrayList(entity), null, DocSyncTypeEnum.ADD);
        this.dataSyncRender.commit(paramBO);
        return entity;
    }

    /**
     * 新增文档集合
     *
     * @param entityList 实体类集合
     */
    public <T extends BaseEntity> List<T> addDocuments(List<T> entityList) {
        if (CollUtil.isEmpty(entityList)) {
            return CollUtil.newArrayList();
        }
        IndexBO indexBO = this.documentHelper.index(entityList.get(0).getClass());
        entityList.forEach(f -> this.entityMetaObjectHandler.fill(FieldFillEnum.INSERT, indexBO, f));
        IndexUpdateParamBO paramBO = this.buildIndexUpdateParamBO(indexBO, entityList, null, DocSyncTypeEnum.ADD);
        this.dataSyncRender.commit(paramBO);
        return entityList;
    }

    /**
     * 更新文档
     *
     * @param entity 实体类
     * @return Boolean
     */
    public <T extends BaseEntity> T updateDocumentById(T entity) {
        String id = this.documentHelper.id(entity);
        IdQueryWrapperBuilder<T> idQueryWrapperBuilder = new IdQueryWrapperBuilder<>(ClassUtil.getClass(entity), id);
        IdQueryWrapper idQueryWrapper = idQueryWrapperBuilder.build();
        T source = this.searchById(idQueryWrapper);
        if(Objects.isNull(source)) {
            throw new AppException(MessageEnum.PARAM_NULL);
        }
        IndexBO indexBO = idQueryWrapper.getIndexInfo();
        BeanUtil.copyProperties(entity, source, CopyOptions.create().ignoreNullValue());
        this.entityMetaObjectHandler.fill(FieldFillEnum.UPDATE, indexBO, source);
        IndexUpdateParamBO paramBO = this.buildIndexUpdateParamBO(indexBO, CollUtil.newArrayList(source), null, DocSyncTypeEnum.UPDATE);
        this.dataSyncRender.commit(paramBO);
        return source;
    }

    /**
     * 删除
     *
     * @param wrapper 删除的匹配条件
     */
    public void deleteByIds(DeleteByIdsWrapper wrapper) {
        Assert.notNull(wrapper, "DeleteByIdsWrapper can not null");
        IndexBO indexBO = wrapper.getIndexInfo();
        IndexUpdateParamBO paramBO = this.buildIndexUpdateParamBO(indexBO, null, wrapper.getIdList(), DocSyncTypeEnum.DEL);
        paramBO.setIgnoreSync(wrapper.getIgnoreSync());
        this.dataSyncRender.commit(paramBO);
    }

    /**
     * 隐藏指定版本的文档
     * 注：该操作不会在实例间同步
     *
     * @param list    文档列表
     * @param yesOrNo 是否可见
     */
    public <T extends BaseEntity> void switchDoc(List<T> list, YesOrNoEnum yesOrNo) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        IndexBO indexBO = this.documentHelper.index(list.get(0).getClass());
        list.forEach(t -> {
            // 变更为不可见
            t.setVisible(yesOrNo.getCode());
            String id = t.getId();
            int version = t.getVersion();
            BooleanQuery.Builder delBuilder = new BooleanQuery.Builder();
            delBuilder.add(new TermQuery(new Term(StrConstant.D_ID, id)), BooleanClause.Occur.MUST);
            delBuilder.add(IntPoint.newExactQuery(StrConstant.D_VERSION, version), BooleanClause.Occur.MUST);
            this.luceneHelper.updateByQuery(
                    indexBO.getIndexName(), delBuilder.build(), this.documentHelper.toDocument(t));
        });
    }

    /**
     * 构建消息报文
     *
     * @param indexBO  索引信息
     * @param args     实体类对象
     * @param syncType 操作类型
     * @return IndexUpdateParamVO
     */
    private IndexUpdateParamBO buildIndexUpdateParamBO(IndexBO indexBO,
                                                       List<? extends BaseEntity> args, List<String> delIds, DocSyncTypeEnum syncType) {
        IndexUpdateParamBO indexUpdateParamBO = new IndexUpdateParamBO();
        if (CollUtil.isEmpty(args) && CollUtil.isEmpty(delIds)) {
            return indexUpdateParamBO;
        }
        if (Objects.isNull(indexBO)) {
            return indexUpdateParamBO;
        }
        indexUpdateParamBO.setIndexName(indexBO.getIndexName());
        indexUpdateParamBO.setContent(args);
        indexUpdateParamBO.setContentStr(FastJsonUtil.toJsonString(args));
        indexUpdateParamBO.setIdList(delIds);
        indexUpdateParamBO.setClassName(indexBO.getClassName());
        indexUpdateParamBO.setSyncType(syncType);
        return indexUpdateParamBO;
    }
}
